from collections import OrderedDict
import re
from pprint import pprint
import os, traceback
import wx
import json


def popuperror(func):
    def myfunc(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except AssertionError as e:
            wx.LogError(str(e))
        except:
            wx.LogError(traceback.format_exc())

    return myfunc


def filedir():
    return os.path.dirname(__file__)


class CommonType:
    def __init__(self):
        self.typedict = {'%s%s  %s位' % (s, x, y): s + str(x) for s in ['U', 'S'] for x, y in [(1, 8), (2, 16), (4, 32)]}
        self.accudict = {'%3d  精度:%s' % (i, 2 ** i): i for i in range(-3, 64)}
        self.typelist = sorted(self.typedict.keys(), key=lambda a: a[1])
        self.acculist = sorted(self.accudict.keys(), key=lambda a: self.accudict[a])
        with open(os.path.join(filedir(), 'config.json'), 'r', encoding='utf-8') as f:
            self.cf = json.load(f)
        self.init()

    @popuperror
    def init(self):
        self.gui = MainFrame(mobj=self,
                             size=tuple(self.cf['WindowSize']),
                             pos=tuple(self.cf['WindowPosition']),
                             typelist=self.typelist,
                             acculist=self.acculist,
                             defaulttype=self.cf['defaulttype'],
                             defaultaccu=self.cf['defaultaccu'])
        projectpath = self.getprojectpath()
        assert projectpath, '没有找到开发项目'
        filepath = os.path.join(projectpath, self.cf['commontype.h相对路径'])
        with open(filepath, 'r', encoding='utf-8') as f:
            code = f.read()
        code = self.delcomments(code)
        self.typedef, self.define, self.deflineidx = self.getdefinerelation(code)
        typepool = {}
        defpool = {}
        for k, v in self.typedef.items():
            if v not in typepool: typepool[v] = set()
            typepool[v].add(k)
        for k, v in self.define.items():
            if v not in defpool: defpool[v] = set()
            defpool[v].add(k)
        self.typepool, self.defpool = typepool, defpool

    @staticmethod
    def getprojectpath():
        path = os.getcwd()
        projectpath = ''
        while True:
            if '.git' in os.listdir(path):
                projectpath = path
                break
            else:
                temp = os.path.dirname(path)
                if path == temp:
                    break
                else:
                    path = temp
        return projectpath

    @staticmethod
    def delcomments(code):
        def getstridx(code, substr):
            fstart = 0
            l = []
            try:
                while True:
                    idx = code.index(substr, fstart)
                    fstart = idx + 1
                    l.append(idx)
            except ValueError:
                pass
            return l

        def getnearidx_inlist(num, sortedlist):
            for i, x in enumerate(sortedlist):
                if x > num:
                    highidx = i
                    lowidx = None if i == 0 else (i - 1)
                    break
            else:
                highidx = None
                lowidx = len(sortedlist) - 1 if len(sortedlist) else None
            return lowidx, highidx

        stridx = {'"': [], '//': [], '/*': [], '*/': [], '\n': []}

        for k in stridx.keys():
            stridx[k] = getstridx(code, k)
        codemaxidx = len(code) - 1 if len(code) else 0

        charidx = {}
        stridx_copy = stridx.copy()
        codestat = 'normal'
        startidx = 0
        statstart = {'"': 'string', '//': 'linecomment', '/*': 'comment'}
        statend = {'string': '"', 'linecomment': '\n', 'comment': '*/'}
        include_endchar = {'string': True, 'linecomment': False, 'comment': True}
        coderange = []
        length_statend = {k: len(v) for k, v in statend.items()}
        retain = {'normal', 'string'}

        while True:
            if codestat == 'normal':
                charidx = {k: v[0] for k, v in stridx_copy.items() if v and k in statstart}
                charidxrev = {v: k for k, v in charidx.items()}
                if charidx:
                    minidx = min(charidx.values())
                    minchar = charidxrev[minidx]

                    endidx = minidx - 1
                    if endidx >= startidx:
                        if codestat in retain:
                            coderange.append((startidx, endidx))

                    startidx = minidx
                    codestat = statstart[minchar]

                    stridx_copy[minchar].pop(0)
                else:
                    if codestat in retain:
                        coderange.append((startidx, codemaxidx))
                    break
            else:
                endlist = stridx_copy[statend[codestat]]
                _, lihigh = getnearidx_inlist(startidx, endlist)
                if lihigh == None:
                    if codestat in retain:
                        coderange.append((startidx, codemaxidx))
                    break
                else:
                    if include_endchar[codestat]:
                        endidx = endlist[lihigh] + length_statend[codestat] - 1
                    else:
                        endidx = endlist[lihigh] - 1

                    if codestat in retain:
                        coderange.append((startidx, endidx))

                    stridx_copy[statend[codestat]] = endlist[lihigh + 1:]  # 把前面的删掉

                    for k in stridx.keys():  # 当前endidx之前的idx数据已经没有意义了,删去
                        templist = stridx_copy[k]
                        lilow, _ = getnearidx_inlist(endidx, templist)
                        if not lilow == None:
                            stridx_copy[k] = templist[lilow + 1:]

                    codestat = 'normal'
                    if endidx >= codemaxidx:
                        break
                    else:
                        startidx = endidx + 1

        newcode = []
        for idxlo, idxhi in coderange:
            newcode.append(code[idxlo:idxhi + 1])

        return ''.join(newcode)

    @staticmethod
    def delcomments_2(code):
        NORMAL = 3
        CODE_LEN = len(code)

        if not code: return ''
        # 0->string    1->linecomment   2->comment    3->normal
        # statstart = {'"': 'string', '//': 'linecomment', '/*': 'comment'}
        # statend = {'string': '"', 'linecomment': '\n', 'comment': '*/'}
        # include_endchar = {'string': True, 'linecomment': False, 'comment': True}
        # length_statend = {k: len(v) for k, v in statend.items()}
        statstart = ('"', '//', '/*')
        statend = ('"', '\n', '*/')
        include_endchar = (True, False, True)
        length_statend = [len(x) for x in statend]
        STATNUM = len(statstart)
        retain = {0, 3}
        coderange = []

        codestat = NORMAL
        lastidx = 0
        codeidx = CODE_LEN
        while True:
            if codestat == NORMAL:
                codeidx=CODE_LEN
                for stat, substr in enumerate(statstart):
                    try:
                        idx = code.index(substr, lastidx)
                        if idx < codeidx:
                            codeidx = idx
                            codestat = stat
                    except ValueError:
                        pass
                if codeidx==CODE_LEN:
                    coderange.append((lastidx,CODE_LEN-1))
                    break
                elif:
                    ...

    @staticmethod
    def getdefinerelation(code):
        typedef = OrderedDict()
        define = OrderedDict()
        deflineidx = OrderedDict()

        codelines = code.split('\n')

        indefstate = False
        defname = ''
        deftext = ''

        defreg = re.compile(r'#define[\s]+([\w]+(\(.*?\))?)[\s]+(.*)')

        for i, line in enumerate(codelines):
            if line.startswith('#define'):
                temp = defreg.search(line)
                if not temp: continue
                defname = temp.groups()[0]
                deflineidx[defname] = i
                if line.endswith('\\\n'):
                    deftext = temp.groups()[2][:-1] if temp.groups()[2] else ''
                    indefstate = True
                else:
                    if temp.groups()[2]:
                        define[defname] = temp.groups()[2]
                        continue
            else:
                if indefstate:
                    if line.endswith('\\\n'):
                        deftext += line[:-2]
                    else:
                        deftext += line
                        define[defname] = deftext.strip()
                        indefstate = False

        for x in re.finditer(r'typedef[\s]+([\w]+)[\s]+([\w]+)[\s]*?;', code):
            typedef[x.groups()[1]] = x.groups()[0]

        return typedef, define, deflineidx

    def findall_vartype(self, typestr, accustr):
        vartype = self.typedict[typestr]
        varaccu = self.accudict[accustr]
        if varaccu > 9:
            varaccu = str(varaccu)
        elif varaccu >= 0:
            varaccu = '0' + str(varaccu)
        elif varaccu >= -9:
            varaccu = 'N0' + str(-varaccu)
        else:
            varaccu = 'N' + str(-varaccu)
        targettype = 'FIXED_%s_%s' % (vartype, varaccu)
        text = ''
        if targettype in self.typepool:
            text += '\n'.join(self.typepool[targettype])
        if targettype in self.defpool:
            text += '\n' + '\n'.join(self.defpool[targettype])
        return text

    def findreference(self, string):
        textlist = []
        newstr = string
        while True:
            if newstr in self.define:
                temp = self.define[newstr]
                textlist.append(temp)
                newstr = temp
            elif newstr in self.typedef:
                temp = self.typedef[newstr]
                textlist.append(temp)
                newstr = temp
            else:
                break
        if re.match(r'[US][124]_[\w]+_T[A-Z]', string):
            correlate = [x + string.strip() + y for x, y in
                         [('RAW_', '(phy)'), ('PWR_', ''), ('MAX_', ''), ('MIN_', '')]]
            textlist += ['%s( -> )    %s' % (x, self.define[x]) for x in correlate if x in self.define]
            textlist += ['%s( -> )    %s' % (x, self.typedef[x]) for x in correlate if x in self.typedef]
        return '\n'.join(textlist)

    def closedeal(self):
        rect = self.gui.GetScreenRect().Get()
        self.cf["WindowPosition"] = [rect[0], rect[1]]
        self.cf["WindowSize"] = [rect[2], rect[3]]
        self.cf['defaulttype'] = self.gui.typechoice.GetSelection()
        self.cf['defaultaccu'] = self.gui.accuchoice.GetSelection()
        with open(os.path.join(filedir(), 'config.json'), 'w', encoding='utf-8') as f:
            json.dump(self.cf, f, ensure_ascii=False, indent=4)


class MainFrame(wx.Frame):
    def __init__(self, mobj=None, size=(1000, 800), pos=(800, 200), typelist=None, acculist=None, defaulttype=0,
                 defaultaccu=0):
        wx.Frame.__init__(self, None, size=size, pos=pos)
        self.mobj = mobj
        self.SetBackgroundColour(wx.Colour(224, 238, 238))

        self.pl = wx.Panel(self, size=size)

        self.typechoice = wx.Choice(self.pl, choices=typelist, size=(100, 40))
        self.accuchoice = wx.Choice(self.pl, choices=acculist, size=(100, 40))
        self.text = wx.TextCtrl(self.pl, style=wx.TE_PROCESS_ENTER)
        self.log = wx.TextCtrl(self.pl, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP, size=(100, 100))
        self.bt0 = wx.Button(self.pl, label='⚪', size=(60, 30))
        self.bt1 = wx.Button(self.pl, label='⚪', size=(60, 30))

        textattr = wx.TextAttr()
        textattr.SetFontFaceName('Consolas')
        textattr.SetFontSize(20)
        self.log.SetDefaultStyle(textattr)

        self.sizer = wx.GridBagSizer(5, 5)
        self.subsizer = wx.BoxSizer()
        self.subsizer.Add(self.typechoice, proportion=1, flag=wx.EXPAND | wx.RIGHT, border=6)
        self.subsizer.Add(self.accuchoice, proportion=1, flag=wx.EXPAND | wx.LEFT, border=6)
        self.sizer.Add(20, 20, pos=(0, 0), span=(1, 1), flag=wx.EXPAND)
        self.sizer.Add(self.subsizer, pos=(1, 0), span=(1, 1), flag=wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, border=6)
        self.sizer.Add(self.bt0, pos=(1, 1), span=(1, 1), flag=wx.TOP | wx.RIGHT | wx.LEFT, border=6)
        self.sizer.Add(self.text, pos=(2, 0), span=(1, 1), flag=wx.EXPAND | wx.RIGHT | wx.LEFT, border=6)
        self.sizer.Add(self.bt1, pos=(2, 1), span=(1, 1), flag=wx.RIGHT | wx.LEFT, border=6)
        self.sizer.Add(self.log, pos=(3, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL, border=6)

        self.sizer.AddGrowableCol(0)
        self.sizer.AddGrowableRow(3)
        self.pl.SetSizer(self.sizer)

        self.Bind(wx.EVT_BUTTON, self.on_button0, self.bt0)
        self.Bind(wx.EVT_BUTTON, self.on_button1, self.bt1)
        self.Bind(wx.EVT_TEXT_ENTER, self.on_button1, self.text)
        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.typechoice.SetSelection(defaulttype)
        self.accuchoice.SetSelection(defaultaccu)

        self.Centre()
        self.Show()

    def on_button0(self, event):
        typestr = self.typechoice.GetStringSelection()
        accustr = self.accuchoice.GetStringSelection()
        text = self.mobj.findall_vartype(typestr, accustr)
        if not text: text = '--没找到--'
        self.log.SetValue(text)

    def on_button1(self, event):
        string = self.text.GetValue()
        if string:
            text = self.mobj.findreference(string)
            if not text: text = '--没找到--'
            self.log.SetValue(text)

    def on_close(self, event):
        try:
            self.mobj.closedeal()
        finally:
            event.Skip()


# if __name__ == '__main__':
#     app = wx.App()
#     CommonType()
#     app.MainLoop()

if __name__ == '__main__':
    with open('LEC4HE_AR_req_DGX2021_77_20211109.a2l', 'r') as f:
        code = f.read()
    x = CommonType.delcomments(code)
    with open('temp.a2l', 'w') as f:
        f.write(x)
